<?php
/**
* 名称：PDO扩展类。
* 描述：继承自PDO类，包含原PDO类所有功能，并针对数据库常用扩展相关功能。
*/
final class EPDO extends PDO{
    /**
    * 描述：用于EPDO类的扩展属性，须在实例化时做为配置参数传入。
    */
    private $ExtentAttribute=NULL;

    /**
    * 描述：保存EPDO扩展方法执行后的错误描述信息。
    */
    private $ExtensionMethodErrorInfo=NULL;

    /**
    * 描述：EPDO构造函数，参数以数组形式传入。
    * @param  string $config['DSN']             必选 服务连接信息。
    * @param  string $config['User']            必选 用户名。
    * @param  string $config['Password']        必选 密码。
    * @param  array  $config['Options']         可选 数据源配置选项。
    * @param  array  $config['Attribute']       可选 PDO原生属性设置。
    * @param  array  $config['ExtendAttribute'] 可选 EPDO属性设置。
    * @return void
    */
    public function __Construct($config){
        parent::__construct($config['DSN'],$config['User'],$config['Password'],$config['Options']);
        foreach($config['Attribute'] as $key=>$value){
            $this->SetAttribute($key,$value);
        }
        $this->ExtentAttribute=$config['ExtendAttribute'];
    }

    /**
    * 描述：向表内插入数据，成功返回TRUE，失败返回FALSE。
    * @param  string $tableName 必选 数据表名。
    * @param  array  $data      必选 键值对左部为字段名，右部为值。
    * @return bool
    */
    public function Insert($table,$data){
        $fieldNameArray=array_keys($data);
        $fileValueArray=array();
        foreach($fieldNameArray as $fileName){
            $fileValueArray[$fileName]=':'.$fileName;
        }
        $sql=sprintf('insert into %s(%s)values(%s)',$table,join(',',$fieldNameArray),implode(',',$fileValueArray));
        $pdoStatement=$this->Prepare($sql);
        $this->BoundParameters($pdoStatement,$data);
        return $this->ExecuteHandler($pdoStatement);
    }

    /**
    * 描述：根据单个字段值匹配删除数据,成功返回删除的行数（包括0行），失败返回FALSE。
    * @param  string      $tableName  必选 数据表名。
    * @param  string      $fieldName  必选 字段名。
    * @param  mix         $fieldValue 必选 字段值。
    * @return bool|number
    */
    public function DeleteRowsByValue($tableName,$fieldName,$fieldValue){
        $pdoStatement=$this->Prepare(sprintf('delete from %s where %s=:%s',$tableName,$fieldName,$fieldName));
        $argument=array($fieldName=>$fieldValue);
        $this->BoundParameters($pdoStatement,$argument);
        return $this->ExecuteHandler($pdoStatement,'rowCount');
    }

    /**
    * 描述：根据单个字段值修改数据,成功返回修改的行数（包括0行），失败返回FALSE。
    * @param  string      $tableName  必选 数据表名。
    * @param  string      $fieldName  必选 字段名。
    * @param  mix         $fieldValue 必选 字段值。
    * @param  array       $data       必选 键值对，左部为字段名，右部为值。
    * @return bool
    */
    public function UpdateRowsByValue($tableName,$fieldName,$fieldValue,$data){
        $sqlPart=NULL;
        foreach(array_keys($data) as $name){
            $sqlPart.=sprintf('%s=:%s,',$name,$name);
        }
        $sql=sprintf(
            '%s %s where %s=:%s',
            sprintf('update %s ',$tableName),
            sprintf('set %s',substr($sqlPart,0,strlen($sqlPart)-1)),
            $fieldName,
            $fieldName
        );
        $pdoStatement=$this->Prepare($sql);
        $newData=array_merge($data,array($fieldName=>$fieldValue));
        $this->BoundParameters($pdoStatement,$newData);
        return $this->ExecuteHandler($pdoStatement);
    }

    /**
    * 描述：根据单个字段值查询所有匹配数据，成功则返回数据关联数组，失败则返回NULL。
    * @param  string     $tableName       必选 数据表名。
    * @param  string     $fieldName       必选 字段名。
    * @param  mix        $fieldValue      必选 字段值。
    * @param  array      $queryFieldsName 可选 要查询的字段，默认值查询全部字段，即为*号。
    * @return array|NULL
    */
    public function GetRowsByValue($tablename,$fieldname,$fieldvalue,$queryFieldsName='*'){
        $argument=array($fieldvalue);
        $sql=sprintf('select %s from %s where %s=?',$queryFieldsName,$tablename,$fieldname);
        $pdoStatement=$this->Prepare($sql);
        $this->BoundParameters($pdoStatement,$argument);
        return $this->ExecuteHandler($pdoStatement,'fetch');
    }

    /**
    * 描述：根据单个字段值一条数据，成功则返回数据关联数组，失败则返回NULL。
    * @param  string     $tableName       必选 数据表名。
    * @param  string     $fieldName       必选 字段名。
    * @param  mix        $fieldValue      必选 字段值。
    * @param  array      $queryFieldsName 可选 要查询的字段，默认值查询全部字段，即为*号。
    * @return array|NULL
    */
    public function GetRowByValue($tablename,$fieldname,$fieldvalue,$queryFieldsName='*'){
        $argument=array($fieldvalue);
        $sql=sprintf('select %s from %s where %s=? limit 0,1',$queryFieldsName,$tablename,$fieldname);
        $pdoStatement=$this->Prepare($sql);
        $this->BoundParameters($pdoStatement,$argument);
        $temp=$this->ExecuteHandler($pdoStatement,'fetch');
        if($temp){
            $temp=$temp[0];
        }
        return $temp;
    }

    /**
    * 描述：查询数据，可选绑定参数、可先分页。
    * @param[0]    string 必选  $sql                     SQL语句。
    * @param[1(2)] string 可选 $queryArguments          SQL参数。数字数组形式，按形参顺序排值，(2)代表该参数可以直接是param[2]。
    *                                                        也就是说当SQL语句没有参数，但又想分页，可以直接传分布参数。
    * @param[2]    string 可选 $pagingArguments         分页参数。
    * #CurrentPage  number 可选 $param[2]['CurrentPage'] 当前页。
    * #PageSize     number 可选 $param[2]['PageSize']    每页显示N条。
    * @return array|NULL|FALSE                              查询失败返回FALSE。
    *                                                        查询成功无数据返回NULL。
    *                                                        查询成功无分页返回数据关联数组。
    *                                                        查询成功有分页见如下书明。
    * #CurrentPage 当前页。
    * #PageSize    每页显示N条。
    * #RowCount    共N条。
    * #PageCount   共N页。
    * #Rows        查询到的数据，也就是关联数组。
    */
    public function GetRows(){
        #定义标量。
        list($arguments,$argumentCount,$sql,$queryArguments,$pagingArguments,$isPaging,$isParameterized)=array(
            func_get_args(),func_num_args(),NULL,NULL,NULL,FALSE,FALSE
        );
        #析获参数。
        if(0===$argumentCount){
            trigger_error('Missing argument 1 for EPDO::GetRows().',E_USER_ERROR);
        }
        else{
            #检查第1个参数的合规性。
            if('string'===gettype($arguments[0])){
                $sql=$arguments[0];
            }
            else{
                trigger_error(sprintf('Argument 1 \'SQL\'\'s datatype is not String for EPDO::GetRows().'),E_USER_ERROR);
            }
            #给出2个以上参数的情况。
            if(1===$argumentCount){
                $isParameterized=FALSE;
            }
            elseif(2===$argumentCount){
                if(array_key_exists('CurrentPage',$arguments[1])){
                    list($pagingArguments,$isPaging)=array($arguments[1],TRUE);
                }
                else{
                    list($queryArguments,$isParameterized)=array($arguments[1],TRUE);
                }
            }
            else{
                list($queryArguments,$pagingArguments,$isPaging,$isParameterized)=array(
                    $arguments[1],
                    $arguments[2],
                    TRUE,
                    TRUE
                );
            }
        }
        #执行逻辑。
        if($isPaging){
            #获取该SQL查询共获取行数
            $sqlCount='select count(*) '.substr($sql,strpos($sql,'from'),strlen($sql));
            $pdoStatementTemp=$this->Prepare($sqlCount);
            if($isParameterized){
                $this->BoundParameters($pdoStatementTemp,$queryArguments);
            }
            $pdoStatementTemp->Execute();
            $resultTemp=$pdoStatementTemp->Fetch();
            $rowCount=$resultTemp['count(*)'];
            #获取分页参数。
            list($currentPage,$pageSize)=array(NULL,NULL);
            if(isset($pagingArguments['CurrentPage'])){
                $currentPage=$pagingArguments['CurrentPage'];
            }
            if(isset($pagingArguments['PageSize'])){
                $pageSize=$pagingArguments['PageSize'];
            }
            //-
            if($currentPage<1){
                $currentPage=1;
            }
            if($pageSize<1){
                $pageSize=1;
            }
            #计算分页参数。
            $pageCount=$rowCount/$pageSize;
            if($pageCount<1){
                $pageCount=1;
            }
            else{
                $pageCount=ceil($pageCount);
            }
            //-
            if($currentPage>$pageCount){
                $currentPage=$pageCount;
            }
            $limitStart=(($currentPage-1)*$pageSize);
            #获取数据行
            $pdoStatement=$this->Prepare($sql.' limit '.$limitStart.','.$pageSize);
            if($isParameterized){
                $this->BoundParameters($pdoStatement,$queryArguments);
            }
            $result=array(
                'CurrentPage'=>$currentPage,
                'PageSize'=>$pageSize,
                'RowCount'=>$rowCount,
                'PageCount'=>$pageCount,
                'Rows'=>$this->ExecuteHandler($pdoStatement,'fetch')
            );
        }
        else{
            $pdoStatement=$this->Prepare($sql);
            if($isParameterized){
                $this->BoundParameters($pdoStatement,$queryArguments);
            }
            $result=$this->ExecuteHandler($pdoStatement,'fetch');
        }
        return $result;
    }

    /**
    * 描述：参数化执行SQL请求。
    * @param  string $sql       必选 SQL语句。
    * @param  string $arguments 必选 查询参数。
    * @return array|NULL|FALSE
    */
    public function ExecParameterized($sql,$arguments){
        $pdoStatement=$this->Prepare($sql);
        $this->BoundParameters($pdoStatement,$arguments);
        return $this->ExecuteHandler($pdoStatement,'bool');
    }

    /**
    * 描述：返回使用使用EPDO扩展方法的错误，供调用和错误处理使用。
    * @param  string 必选 $value 需绑定的字符。
    * @return string
    */
    public function GetExtensionMethodErrorInfo(){
        return $this->ExtensionMethodErrorInfo;
    }

    /**
    * 描述：绑定关键字前后缀。
    * @param  string 必选 $value 需绑定的字符。
    * @return string
    */
    public function BoundModifier($value){
        if(isset($this->ExtentAttribute['Modifier']['Prefix'])){
            $value=$this->ExtentAttribute['Modifier']['Prefix'].$value;
        }
        if(isset($this->ExtentAttribute['Modifier']['Suffix'])){
            $value=$value.$this->ExtentAttribute['Modifier']['Suffix'];
        }
        return $value;
    }

    /**
    * 描述：去掉以"某个符号"为间隔的规律字符串的前后多余符合，专供MYSQL的CONTACT函数使用。
    * @param  string 必选  $value 过滤的字符串。
    * @param  string 可选 $value 指定过滤的符号。
    * @return string
    */
    public static function MySQL_FilterForContact($value,$sign='；'){
        $stringLength=mb_strlen(trim($value));
        if(mb_substr($value,0,1)==$sign){
            $value=mb_substr($value,1,$stringLength);
        }
        $stringLength=mb_strlen(trim($value));
        if(mb_substr($value,$stringLength-1,1)==$sign){
            $value=mb_substr($value,0,$stringLength-1);
        }
        return $value;
    }

    /**
    * 描述：SQL请求失败时的错误处理函数。
    * @param  string 必选 $object 出错的PDO或PDOStatement对象。
    * @return void
    */
    private function ErrorHandler(&$object){
        if($object->errorCode()!='00000'){
            error_log(implode('---',$object->errorInfo()));
            $this->ExtensionMethodErrorInfo=implode('<br/>',$object->errorInfo());
        }
    }

    /**
    * 描述：EPDO的私有辅助方法，在参数化查询时，为PDOStatement对象绑定参数。
    * @param  pdostatement 必选 $pdoStatement       PDOStatement对象。
    * @param  array        必选 $arguments 参数数组，可以是关联数组，也可以是普通数组，将采用不同的方式处理。
    * @return void
    */
    private function BoundParameters(&$pdoStatement,&$arguments){
        $isAssoc=array_keys($arguments)!==range(0,count($arguments)-1);
        $values=array();
        if($isAssoc){
            foreach($arguments as $field=>$value){
                $isSpecial=(gettype($value)=='array') ? TRUE:FALSE;
                $values[$field]=$isSpecial ? $value[0]:$value;
                $type=$isSpecial ? (count($value)>=2 ? $value[1]:PDO::PARAM_STR):PDO::PARAM_STR;
                $length=$isSpecial ?(count($value)>=3 ? $value[2]:NULL ):NULL;
                $options=$isSpecial ? (count($value)>=4 ? $value[3]:NULL ):NULL;
                $pdoStatement->BindParam(':'.$field,$values[$field],$type,$length,$options);
            }
        }
        else{
            foreach($arguments as $key=>$value){
                $isSpecial=(gettype($value)=='array') ? TRUE:FALSE;
                $values[$key]=$isSpecial ? $value[0]:$value;
                $type=$isSpecial ? (count($value)>=2 ? $value[1]:PDO::PARAM_STR):PDO::PARAM_STR;
                $length=$isSpecial ?(count($value)>=3 ? $value[2]:NULL ):NULL;
                $options=$isSpecial ? (count($value)>=4 ? $value[3]:NULL ):NULL;
                $pdoStatement->BindParam($key+1,$values[$key],$type,$length,$options);
            }
        }
        unset($value);
    }

    /**
    * 描述：EPDO扩展方法逻辑返回值处理执行的结果。
    * @param  pdoStatement $object PDOStatement对象。
    * @return array|NULL|FALSE|0
    */
    public function ExecuteHandler(&$object,$type='fetch') {
        $result=$object->Execute();
        if($result){
            if('fetch'===$type){ //bool,fetch,rowCount
                if(0===count($result)){
                    $result=NULL;
                }
                else{
                    $result=$object->FetchAll(PDO::FETCH_ASSOC);
                }
            }
            else{
                if('rowCount'===$type){
                    $result=$object->rowCount();
                }
                else{
                    $result=TRUE;
                }
            }
        }
        $this->ErrorHandler($object);
        return $result;
    }
}
?>